﻿#include <vtkfmt/format.h>
#include <vtkfmt/args.h>
#include <algorithm>
#include <iostream>
#include "MeshDictFormater.h"
#include "catalyst.hpp"
#include "MeshDictNodeName.h"
#include "BoundingBoxNodeName.h"
#include "easyloggingpp.h"

std::string MeshDictFormater::FormatMeshDict(const conduit_cpp::Node& node)
{
  std::string meshDictFormat = "";

  FormatMeshDictHeader_i(meshDictFormat);
  
  if (node.has_child(name_surfaceFile))
  {
    FormatSurfaceFile_i(node[name_surfaceFile], meshDictFormat);
  }

  if (node.has_child(name_maxCellSize))
  {
    FormatMaxCellSize_i(node[name_maxCellSize], meshDictFormat);
  }

  if (node.has_child(name_localRef))
  {
    FormatLocalRefinement_i(node[name_localRef], meshDictFormat);
  }

  if (node.has_child(name_objRef))
  {
    FormatObjectRefinement_i(node[name_objRef], meshDictFormat);
  }

  if (node.has_child(name_geoRef))
  {
    FormatGeometryRefinement_i(node[name_geoRef], meshDictFormat);
  }
  if (node.has_child(name_boundaryLayers))
  {
    FormatBoundaryLayer_i(node[name_boundaryLayers], meshDictFormat);
  }
  FormatRenameBoundary_i(meshDictFormat);

  FormatWorkFlowControl_i(meshDictFormat);

  FormatMeshDictTail_i(meshDictFormat);

  return meshDictFormat;
}

void MeshDictFormater::FormatMeshDictHeader_i(std::string& meshDict)
{
  meshDict += R"(
/*--------------------------------*- C++ -*----------------------------------*\
| =========                 |                                                |
| \\      /  F ield         | cfMesh: A library for mesh generation          | 
|  \\    /   O peration     |                                                |
|   \\  /    A nd           | Author: Franjo Juretic                         | 
|    \\/     M anipulation  | E-mail: franjo.juretic@c-fields.com            |
\*---------------------------------------------------------------------------*/

FoamFile
{
    version   2.0;
    format    ascii;
    class     dictionary;
    location  "system";
    object    meshDict;
}

// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //

)";
}

void MeshDictFormater::FormatSurfaceFile_i(const conduit_cpp::Node& node, std::string& meshDict)
{
  std::string view = R"(surfaceFile "{_surfaceFile}";

)";

  fmt::dynamic_format_arg_store<fmt::format_context> args;
  args.push_back(fmt::arg("_surfaceFile", node.as_string()));
  
  meshDict += fmt::vformat(view, args);
}

void MeshDictFormater::FormatMaxCellSize_i(const conduit_cpp::Node& node, std::string& meshDict)
{
  std::string view = R"(maxCellSize {_maxCellSize};

)";

  fmt::dynamic_format_arg_store<fmt::format_context> args;
  args.push_back(fmt::arg("_maxCellSize", node.to_double()));

  meshDict += fmt::vformat(view, args);
}

void MeshDictFormater::FormatLocalRefinement_i(const conduit_cpp::Node& node, std::string& meshDict)
{
  auto childer_Number = node.number_of_children();
  if (0 == childer_Number)
  {
    return;
  }

  std::string refineView = R"(
    {_fileName}
    {{
        cellSize {_cellSzie};
    }})";

  std::string totalRefinements = "";
  for (conduit_index_t index = 0; index < childer_Number; ++index)
  {
    auto child_Node = node.child(index);
    fmt::dynamic_format_arg_store<fmt::format_context> args;
    args.push_back(fmt::arg("_fileName", child_Node.name()));
    args.push_back(fmt::arg("_cellSzie", child_Node[name_cellSize].to_double()));
    totalRefinements += fmt::vformat(refineView, args);
  }

  std::string view = R"(
localRefinement
{{
    {_totalRefinements}
}})";

  fmt::dynamic_format_arg_store<fmt::format_context> args;
  args.push_back(fmt::arg("_totalRefinements", totalRefinements));

  meshDict += fmt::vformat(view, args);
}

void MeshDictFormater::FormatObjectRefinement_i(const conduit_cpp::Node& node, std::string& meshDict) 
{
  auto childer_Number = node.number_of_children();
  if (0 == childer_Number)
  {
    return;
  }
  std::string totalRefinements = "";
  for (conduit_index_t index = 0; index < childer_Number; ++index)
  {
    auto child_Node = node.child(index);
    auto type = ObjectRefinements.at(child_Node[name_type].as_string());
    switch (type)
    {
      case BOX:
      {       
        FormatBoxRefinement_i(child_Node, totalRefinements);
        break;
      }
      case CONE: 
      {
        FormatConeRefinement_i(child_Node, totalRefinements);
        break;
      }
      case SPHERE:
      {
        FormatSphereRefinement_i(child_Node, totalRefinements);
        break;
      }
      case LINE:
      {
        FormatLineRefinement_i(child_Node, totalRefinements);
        break;
      }
      default:
      break;
    }
  }

    std::string view = R"(
objectRefinements
{{
    {_totalRefinements}
}})";

  fmt::dynamic_format_arg_store<fmt::format_context> args;
  args.push_back(fmt::arg("_totalRefinements", totalRefinements));
  meshDict += fmt::vformat(view, args);
}
void MeshDictFormater::FormatBoxRefinement_i(const conduit_cpp::Node& node,std::string& assembleMeshDict)
{
  std::string refineView = R"(
	{_Refine}
	{{
		type	{_type};
		cellSize {_cellSize};
		centre	({_x} {_y} {_z});
		lengthX	{_lengthX};
		lengthY	{_lengthY};
		lengthZ	{_lengthZ};
		refinementThickness {_thickness};
	}})";
  fmt::dynamic_format_arg_store<fmt::format_context> args;
  args.push_back(fmt::arg("_Refine", node.name()));
  args.push_back(fmt::arg("_type", node[name_type].as_string()));
  args.push_back(fmt::arg("_cellSize", node[name_cellSize].to_double()));
  args.push_back(fmt::arg("_x", node[name_box_center][name_x].to_double()));
  args.push_back(fmt::arg("_y", node[name_box_center][name_y].to_double()));
  args.push_back(fmt::arg("_z", node[name_box_center][name_z].to_double()));
  args.push_back(fmt::arg("_lengthX", node[name_lengthX].to_double()));
  args.push_back(fmt::arg("_lengthY", node[name_lengthY].to_double()));
  args.push_back(fmt::arg("_lengthZ", node[name_lengthZ].to_double()));
  args.push_back(fmt::arg("_thickness", node[name_refThickness].to_double()));
  assembleMeshDict += fmt::vformat(refineView, args);
}
void MeshDictFormater::FormatConeRefinement_i(const conduit_cpp::Node& node,std::string& assembleMeshDict)
{
  std::string refineView = R"(
	{_Refine}
	{{
		type	{_type};
		cellSize	{_cellSize};
		p0 ({_x0} {_y0} {_z0});
		p1 ({_x1} {_y1} {_z1});
		radius0	{_radius0};
		radius1	{_radius1};
		refinementThickness {_thickness};
	}})";
  fmt::dynamic_format_arg_store<fmt::format_context> args;
  args.push_back(fmt::arg("_Refine", node.name()));
  args.push_back(fmt::arg("_type", node[name_type].as_string()));
  args.push_back(fmt::arg("_cellSize", node[name_cellSize].to_double()));
  args.push_back(fmt::arg("_x0", node[name_cone_point0][name_x].to_double()));
  args.push_back(fmt::arg("_y0", node[name_cone_point0][name_y].to_double()));
  args.push_back(fmt::arg("_z0", node[name_cone_point0][name_z].to_double()));
  args.push_back(fmt::arg("_x1", node[name_cone_point1][name_x].to_double()));
  args.push_back(fmt::arg("_y1", node[name_cone_point1][name_y].to_double()));
  args.push_back(fmt::arg("_z1", node[name_cone_point1][name_z].to_double()));
  args.push_back(fmt::arg("_radius0", node[name_cone_radius0].to_double()));
  args.push_back(fmt::arg("_radius1", node[name_cone_radius1].to_double()));
  args.push_back(fmt::arg("_thickness", node[name_refThickness].to_double()));
  assembleMeshDict += fmt::vformat(refineView, args);
}
void MeshDictFormater::FormatSphereRefinement_i(const conduit_cpp::Node& node,std::string& assembleMeshDict)
{
  std::string refineView = R"(
	{_Refine}
	{{
		type	{_type};
		centre	({_x} {_y} {_z});
		cellSize {_cellSize};
		radius	{_radius};
		refinementThickness {_thickness};
	}})";
  fmt::dynamic_format_arg_store<fmt::format_context> args;
  args.push_back(fmt::arg("_Refine", node.name()));
  args.push_back(fmt::arg("_type", node[name_type].as_string()));
  args.push_back(fmt::arg("_cellSize", node[name_cellSize].to_double()));
  args.push_back(fmt::arg("_x", node[name_sphere_center][name_x].to_double()));
  args.push_back(fmt::arg("_y", node[name_sphere_center][name_y].to_double()));
  args.push_back(fmt::arg("_z", node[name_sphere_center][name_z].to_double()));
  args.push_back(fmt::arg("_radius", node[name_sphere_radius].to_double()));
  args.push_back(fmt::arg("_thickness", node[name_refThickness].to_double()));
  assembleMeshDict += fmt::vformat(refineView, args);
}
void MeshDictFormater::FormatLineRefinement_i(const conduit_cpp::Node& node,std::string& assembleMeshDict)
{
  std::string refineView = R"(
	{_Refine}
	{{
		type	{_type};
		cellSize	{_cellSize};
		p0 ({_x0} {_y0} {_z0});
		p1 ({_x1} {_y1} {_z1});
		refinementThickness {_thickness};
	}})";
  fmt::dynamic_format_arg_store<fmt::format_context> args;
  args.push_back(fmt::arg("_Refine", node.name()));
  args.push_back(fmt::arg("_type", node[name_type].as_string()));
  args.push_back(fmt::arg("_cellSize", node[name_cellSize].to_double()));
  args.push_back(fmt::arg("_x0", node[name_line_point0][name_x].to_double()));
  args.push_back(fmt::arg("_y0", node[name_line_point0][name_y].to_double()));
  args.push_back(fmt::arg("_z0", node[name_line_point0][name_z].to_double()));
  args.push_back(fmt::arg("_x1", node[name_line_point1][name_x].to_double()));
  args.push_back(fmt::arg("_y1", node[name_line_point1][name_y].to_double()));
  args.push_back(fmt::arg("_z1", node[name_line_point1][name_z].to_double()));
  args.push_back(fmt::arg("_thickness", node[name_refThickness].to_double()));
  assembleMeshDict += fmt::vformat(refineView, args);
}

void MeshDictFormater::FormatGeometryRefinement_i(const conduit_cpp::Node& node,std::string& meshDict)
{
  int edge_count = 0;
  int surface_count = 0;
  auto childer_Number = node.number_of_children();
  if (0 == childer_Number)
  {
    return;
  }
  std::string totalEdgeRefinements = "";
  std::string totalSurfaceRefinements = "";

  for (conduit_index_t index = 0; index < childer_Number; ++index)
  {
    auto child_Node = node.child(index);
    auto type = GeometryRefinements.at(child_Node[name_geo_type].as_string());
    switch (type)
    {
      case EDGE:
      {
        FormatEdgeRefinement_i(child_Node, totalEdgeRefinements);
        edge_count++;
        break;
      }
      case SURFACE:
      {
        FormatSurfaceRefinement_i(child_Node, totalSurfaceRefinements);
        surface_count++;
        break;
      }
      default:
        break;
    }
  }
  if (edge_count != 0)
  {
    std::string EdgeMeshview = R"(
edgeMeshRefinement
{{
    {_totalEdgeRefinements}
}})";
    fmt::dynamic_format_arg_store<fmt::format_context> args;
    args.push_back(fmt::arg("_totalEdgeRefinements", totalEdgeRefinements));
    meshDict += fmt::vformat(EdgeMeshview, args);
  }
  if (surface_count != 0)
  {
    std::string surfaceMeshview = R"(
surfaceMeshRefinement
{{
    {_totalSurfaceRefinements}
}})";
    fmt::dynamic_format_arg_store<fmt::format_context> args;
    args.push_back(fmt::arg("_totalSurfaceRefinements", totalSurfaceRefinements));
    meshDict += fmt::vformat(surfaceMeshview, args);
  }
}
void MeshDictFormater::FormatSurfaceRefinement_i(const conduit_cpp::Node& node,std::string& assembleMeshDict)
{
  std::string refineView = R"(
	{_Refine}
	{{
		surfaceFile "{_surfaceFile}";
		cellSize {_cellSize};
		refinementThickness {_thickness};
	}})";
  fmt::dynamic_format_arg_store<fmt::format_context> args;
  args.push_back(fmt::arg("_Refine", node.name()));
  args.push_back(fmt::arg("_surfaceFile", node[name_geoInputName].as_string()));
  args.push_back(fmt::arg("_cellSize", node[name_cellSize].to_double()));
  args.push_back(fmt::arg("_thickness", node[name_refThickness].to_double()));
  assembleMeshDict += fmt::vformat(refineView, args);
}
void MeshDictFormater::FormatEdgeRefinement_i(const conduit_cpp::Node& node,std::string& assembleMeshDict)
{
  std::string refineView = R"(
	{_Refine}
	{{
		edgeFile "{_edgeFile}";
		cellSize {_cellSize};
		refinementThickness {_thickness};
	}})";
  fmt::dynamic_format_arg_store<fmt::format_context> args;
  args.push_back(fmt::arg("_Refine", node.name()));
  args.push_back(fmt::arg("_edgeFile", node[name_geoInputName].as_string()));
  args.push_back(fmt::arg("_cellSize", node[name_cellSize].to_double()));
  args.push_back(fmt::arg("_thickness", node[name_refThickness].to_double()));
  assembleMeshDict += fmt::vformat(refineView, args);

}

void MeshDictFormater::FormatBoundaryLayer_i(const conduit_cpp::Node& node, std::string& meshDict)
{
  auto childer_Number = node.number_of_children();
  if (0 == childer_Number)
  {
    return;
  }

  std::string layerView = R"(
		{_fileName}
		{{
			nLayers {_nLayers};
			thicknessRatio {_thicknessRatio};
			maxFirstLayerThickness {_maxFirstLayerThickness};
		}})";

  std::string totalLayers = "";
  for (conduit_index_t index = 0; index < childer_Number; ++index)
  {
    auto child_node = node.child(index);
    fmt::dynamic_format_arg_store<fmt::format_context> args;
    args.push_back(fmt::arg("_fileName", child_node.name()));
    args.push_back(fmt::arg("_nLayers", child_node[name_nLayers].to_unsigned_int()));
    args.push_back(fmt::arg("_thicknessRatio", child_node[name_thicknessRatio].to_double()));
    args.push_back(fmt::arg("_maxFirstLayerThickness", child_node[name_maxFirstLayerThikness].to_double()));
    totalLayers += fmt::vformat(layerView, args);
  }

  std::string view = R"(
boundaryLayers
{{/*	
  optimiseLayer 1; //actives smoothing boundary layers(activated by default)
  untangleLayers 0;
  optimisationParameters
  {{
    nSmoothNormals 3;
    maxNumIterations 5;
    featureSizeFactor 0.4;
    reCalculateNormals 1;
    relThicknessTol 0.1;
  }}*/
  patchBoundaryLayers
	{{
        {_totalLayers}
	}}
}})";

  fmt::dynamic_format_arg_store<fmt::format_context> args;
  args.push_back(fmt::arg("_totalLayers", totalLayers));

  meshDict += fmt::vformat(view, args);
}

void MeshDictFormater::FormatRenameBoundary_i(std::string& meshDict)
{
  meshDict += R"(
renameBoundary
{
/*
    defaultType     wall;
    newPatchNames
    {
        "driver.*"
        {
            newName car; 
            type  wall;
        }            
				
        "xMin" 
        { 
            newName inlet; 
            type  patch;
        }
        "xMax" 
        { 
            newName outlet; 
            type  patch;
        }
        "yMin" 
        { 
            newName side_wall; 
            type  patch;
        }
        "yMax" 
        { 
            newName side_wall; 
            type  patch;
        }
        "zMin" 
        { 
            newName ground; 
            type  wall;
        }
        "zMax" 
        { 
            newName side_wall; 
            type  patch;

        }
    }

 */   
    defaultType     wall;
    newPatchNames
    {
        "xMin" 
        { 
            newName outlet; 
            type  patch;
        }
        "xMax" 
        { 
            newName inlet; 
            type  patch;
        }
        "yMin" 
        { 
            newName sidewall; 
            type  wall;
        }
        "yMax" 
        { 
            newName sidewall; 
            type  wall;
        }
        "zMin" 
        { 
            newName sidewall; 
            type  wall;
        }
        "zMax" 
        { 
            newName sidewall; 
            type  wall;
        }
    }
}
)";
}

void MeshDictFormater::FormatWorkFlowControl_i(std::string& meshDict)
{
  meshDict += R"(
workflowControls
{
  stopAfter templateGeneration;
  stopAfter surfaceTopology;
  stopAfter surfaceProjection;
  stopAfter patchAssignment;
  stopAfter edgeExtraction;
  stopAfter boundaryLayerGeneration;
  stopAfter meshOptimisation;
  stopAfter boundaryLayerRefinement;
}

)";
}

void MeshDictFormater::FormatMeshDictTail_i(std::string& meshDict)
{
  meshDict += R"(
// ************************************************************************* //
)";
}